package com.yc.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;


import javax.sql.DataSource;
import java.util.Arrays;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    //令牌服务
    @Autowired
    private TokenStore tokenStore;
    //客户端详情服务
    @Autowired
    private ClientDetailsService clientDetailsService;


    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 第一步wlj
     * 客户端配置
     * 用来配置客户端详情服务（ClientDetailsService），客户端详情信息在
     * 这里进行初始化，你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     * 支持哪些客户端来请求
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService);
//        clients.inMemory()// 使用in‐memory存储
//                .withClient("c1")// client_id
//                .secret(new BCryptPasswordEncoder().encode("secret"))
//                //资源服务id
//                .resourceIds("res1")
//                // 该client允许的授权类型  authorization_code,password,refresh_token,implicit,client_credentials
//                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")
//                .scopes("all")// 允许的授权范围  是一个标识
//                .autoApprove(false)//false代表如果是授权码模式 就跳转到授权的页面.
//                  //加上验证回调地址
//                .redirectUris("http://www.baidu.com");
    }



    /**第三步wlj
     *
     * 配置令牌服务
     * 不管是什么模式都需要配置
     * 需要使用到客户端clientDetailsService和令牌服务tokenStore
     */
    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;
    @Bean
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        //客户端详情
        service.setClientDetailsService(clientDetailsService);
        //刷新token
        service.setSupportRefreshToken(true);
        //令牌服务
        service.setTokenStore(tokenStore);

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);

        // 令牌默认有效期2小时
        service.setAccessTokenValiditySeconds(7200);
        // 刷新令牌默认有效期3天
        service.setRefreshTokenValiditySeconds(259200);
        return service;
    }




    /**
     * 第四步wlj
     * 定义授权和令牌端点以及令牌服务
     * 配置令牌访问的url
     * 和令牌服务(令牌的发放.生成和存储)
     *
     * @param endpoints
     * @throws Exception
     * AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型（Grant Types）:
     * authenticationManager：认证管理器，当你选择了资源所有者密码（password）授权类型的时候，请设置
     * 这个属性注入一个 AuthenticationManager 对象。
     * userDetailsService：如果你设置了这个属性的话，那说明你有一个自己的 UserDetailsService 接口的实现，
     * 或者你可以把这个东西设置到全局域上面去（例如 GlobalAuthenticationManagerConfigurer 这个配置对
     * 象），当你设置了这个之后，那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查，用
     * 来确保这个账号是否仍然有效，假如说你禁用了这个账户的话。
     * authorizationCodeServices：这个属性是用来设置授权码服务的（即 AuthorizationCodeServices 的实例对
     * 象），主要用于 "authorization_code" 授权码类型模式。
     *
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                //密码模式所需要的
                .authenticationManager(authenticationManager)
                //授权码模式所需要的
                .authorizationCodeServices(authorizationCodeServices)
                //如果你设置了这个属性的话，那说明你有一个自己的 UserDetailsService 接口的实现
                //.userDetailsService(null)
                //令牌管理服务
                .tokenServices(tokenService())
                //自定义令牌端点URL
              //  .pathMapping("/oauth/token","/user/token")
                //自定义授权端点URL
             //   .pathMapping("/oauth/authorize","/user/authorize")
                //允许POST提交
                .allowedTokenEndpointRequestMethods(HttpMethod.POST);
    }

    /**
     * 第四步wlj 4.1 不配置找不到Bean authorizationCodeServices
     * @return
     */
//    @Bean
//    public AuthorizationCodeServices authorizationCodeServices() {
//        //设置授权码模式的授权码如何 存取，暂时采用内存方式
//        return new InMemoryAuthorizationCodeServices();
//    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        return new JdbcAuthorizationCodeServices(dataSource);//设置授权码模式的授权码如何存取
    }

    /**
     * 第五步wlj
     * 配置令牌端点(Token Endpoint)的安全约束
     * 配置了令牌的URL 哪些人可以访问这些URL 对URL 的约束
     *

     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                //tokenkey这个endpoint当使用JwtToken且使用非对称加密时，资源服务用于获取公钥而开放的，这里指这个endpoint完全公开。
                .tokenKeyAccess("permitAll()")
                //checkToken这个endpoint完全公开
                .checkTokenAccess("permitAll()")
                //允许表单认证
                .allowFormAuthenticationForClients() ;
    }

    /**
     * 客户端认证服务  因为之前使用的是内存模式 直接配置信息就好了
     * 现在使用的是数据库 所以要配置数据库信息
     * @param dataSource
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource) {
        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        ((JdbcClientDetailsService)
                clientDetailsService).setPasswordEncoder( passwordEncoder() );
        return clientDetailsService;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }




}
